﻿using MS.Internal;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Threading;
using UBCon.Core;

namespace UBCon.Navigation
{
    /// <summary>
    /// Arranges child elements into a sinlge line that can oriented horizontally or vertically and child elemnts can be swapped by dragging.
    /// </summary>
   public class SwapPanel : Panel
    {
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------
        #region Constructors

       /// <summary>
        /// Initializes a new instance of the UBCon.Navigation.SwapPanel class.
       /// </summary>
        public SwapPanel()
        {
            RenderTransform = translate;

            BuildAnimation();
        }

        #endregion

        //------------------------------------------------------
        //
        //  Properties
        //
        //------------------------------------------------------
        #region Properties
        
        /// <summary>
        ///Gets or sets a boolean value which indicates whether items can be dragged or not. 
        ///This is a dependency property.
        /// </summary>
        public bool ItemsDraggable
        {
            get { return (bool)GetValue(ItemsDraggableProperty); }
            set { SetValue(ItemsDraggableProperty, value); }
        }

        /// <summary>
        /// Identifies the UBCon.Navigation.SwapPanel.ItemsDraggable dependency property.
        /// </summary>
        public static readonly DependencyProperty ItemsDraggableProperty =
            DependencyProperty.Register("ItemsDraggable", typeof(bool),
            typeof(SwapPanel),
            new FrameworkPropertyMetadata(false, 
                new PropertyChangedCallback(OnItemsDraggablePropertyChanged)));

       /// <summary>
       /// Gets or sets a value that indicates the dimension by which child elements arranged.
       /// </summary>
        public Orientation Orientation
        {
            get { return (Orientation)GetValue(OrientationProperty); }
            set { SetValue(OrientationProperty, value); }
        }

        /// <summary>
        /// Identifies the UBCon.Navigation.SwapPanel.Orientation dependency property.
        /// </summary>
        public static readonly DependencyProperty OrientationProperty =
            DependencyProperty.Register("Orientation", typeof(Orientation), typeof(SwapPanel),
            new FrameworkPropertyMetadata(Orientation.Horizontal,
                FrameworkPropertyMetadataOptions.AffectsMeasure));

        /// <summary>
        /// Gets the value of the UBCon.Navigation.SwapPanel.SwapPanelItemModel attached property for the 
        /// given dependency object.
        /// </summary>
        internal static SwapPanelItem GetSwapPanelItemModel(DependencyObject obj)
        {
            return (SwapPanelItem)obj.GetValue(SwapPanelItemModelProperty);
        }

        /// <summary>
        /// Sets the value of the UBCon.Navigation.SwapPanel.SwapPanelItemModel attached property for the 
        /// given dependency object.
        /// </summary>
        internal static void SetSwapPanelItemModel(DependencyObject obj, SwapPanelItem value)
        {
            obj.SetValue(SwapPanelItemModelProperty, value);
        }

        /// <summary>
        /// Identifies the UBCon.Navigation.SwapPanel.SwapPanelItemModel atteched property.
        /// </summary>
        internal static readonly DependencyProperty SwapPanelItemModelProperty =
            DependencyProperty.RegisterAttached("SwapPanelItemModel", typeof(SwapPanelItem), typeof(SwapPanel));
        
        #endregion

        //------------------------------------------------------
        //
        //  Depenency Property Changed Callbacks
        //
        //------------------------------------------------------
        #region Depenency Property Changed Callbacks
     
       /// <summary>
        /// Invoker everytime when ItemsDraggable property changes.
       /// </summary>
        /// <param name="sender">Type: System.Windows.DependencyObject. The DependencyObject on which the property has changed value.</param>
        /// <param name="e">Type: System.Windows.DependencyPropertyChangedEventArgs. Event data that is issued by any event that tracks changes to the effective value of this property.</param>
        private static void OnItemsDraggablePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            bool newValue = (bool)e.NewValue;
            
            SwapPanel panel = sender as SwapPanel;

            for (int i = 0; i < panel.InternalChildren.Count; i++)
            {
                OutlookBarItem item = panel.InternalChildren[i] as OutlookBarItem;
                if (item != null)
                {
                    item.DragStarted -= panel.item_DragStarted;

                    item.DragDelta -= panel.item_DragDelta;

                    item.DragCompeted -= panel.OnItemDragCompeted;
                }
            }

            if (newValue)
            {
                for (int i = 0; i < panel.InternalChildren.Count; i++)
                {
                    OutlookBarItem item = panel.InternalChildren[i] as OutlookBarItem;
                    if (item != null)
                    {
                        item.DragStarted += panel.item_DragStarted;

                        item.DragDelta += panel.item_DragDelta;

                        item.DragCompeted += panel.OnItemDragCompeted;
                    }
                }
            }
        }

       /// <summary>
        /// Occurs when dragging is finished.
       /// </summary>
        /// <param name="sender">Owner of the event.</param>
        /// <param name="e">Event data for the event.</param>
        private void OnItemDragCompeted(object sender, UBCon.Navigation.DragCompletedEventArgs e)
        {
            OutlookBarItem item = sender as OutlookBarItem;

            InvalidateArrange();

            if (item.RenderTransform != null && item.RenderTransform is TranslateTransform)
            {
                if (this.Orientation == System.Windows.Controls.Orientation.Horizontal)
                {
                    (item.RenderTransform as TranslateTransform).X = 0;
                }
                else 
                {
                    (item.RenderTransform as TranslateTransform).Y = 0;
                }
            }
        }

       /// <summary>
       /// Occurs while child element is being dragged.
       /// </summary>
        /// <param name="sender">Owner of the event.</param>
        /// <param name="e">Event data for the event.</param>
        private void item_DragDelta(object sender, DragDeltaEventArgs e)
        {
            OutlookBarItem barItem = sender as OutlookBarItem;

            Point p = Mouse.GetPosition(this);

            if (cIndex >= 0 && cIndex < InternalChildren.Count && barItem != null &&
                barItem.RenderTransform != null &&
                barItem.RenderTransform is TranslateTransform)
            {
                SwapPanelItem capturedItemModel = (from model in ItemsModel where model.UIElement == barItem select model).FirstOrDefault();

                if (this.Orientation == System.Windows.Controls.Orientation.Horizontal)
                {
                    
                    double distance = p.X - sPoint.X;
                    (barItem.RenderTransform as TranslateTransform).X = distance;
                    bool isRight = distance - pValue > 0, isLeft = !isRight;
                    pValue = distance;
                    
                    if (isRight && cIndex < InternalChildren.Count - 1)
                    {
                        UIElement elt = (from model in ItemsModel where model.Index == cIndex + 1 select model.UIElement).FirstOrDefault();

                        if (elt.Visibility==System.Windows.Visibility.Visible)
                        {
                            SwapPanelItem itemModel = elt.GetValue(SwapPanel.SwapPanelItemModelProperty) as SwapPanelItem;

                            if (itemModel != null && itemModel.XLocation > capturedItemModel.XLocation &&
                                itemModel.EndPoint.X < capturedItemModel.EndPoint.X + distance + cOffset)
                            {
                                capturedItemModel.Index = ++cIndex;

                                itemModel.Index--;

                                cOffset -= itemModel.UIElement.DesiredSize.Width;

                                MoveWE(itemModel.UIElement, barItem, false);

                            }
                        }
                       
                    }
                    else if (isLeft && cIndex > 0)
                    {
                        UIElement elt = (from model in ItemsModel where model.Index == cIndex - 1 select model.UIElement).FirstOrDefault();

                        if (elt.Visibility == System.Windows.Visibility.Visible)
                        {
                            SwapPanelItem itemModel = elt.GetValue(SwapPanel.SwapPanelItemModelProperty) as SwapPanelItem;

                            if (itemModel != null && itemModel.XLocation > capturedItemModel.XLocation + distance + cOffset &&
                                itemModel.XLocation < capturedItemModel.XLocation)
                            {
                                capturedItemModel.Index = --cIndex;

                                itemModel.Index++;

                                cOffset += itemModel.UIElement.DesiredSize.Width;

                                MoveWE(itemModel.UIElement, barItem);
                            }
                        }
                    }
                }
                else
                {
                    double distance = p.Y - sPoint.Y;
                    (barItem.RenderTransform as TranslateTransform).Y = distance;
                    bool isUp = distance - pValue < 0, isDown = !isUp;
                    pValue = distance;

                    if (isDown && cIndex < InternalChildren.Count - 1)
                    {
                        UIElement elt = (from model in ItemsModel where model.Index == cIndex + 1 select model.UIElement).FirstOrDefault();

                        if (elt.Visibility== System.Windows.Visibility.Visible)
                        {
                            SwapPanelItem itemModel = elt.GetValue(SwapPanel.SwapPanelItemModelProperty) as SwapPanelItem;

                            if (itemModel != null && itemModel.YLocation > capturedItemModel.YLocation &&
                                itemModel.EndPoint.Y < capturedItemModel.EndPoint.Y + distance + cOffset)
                            {
                                capturedItemModel.Index = ++cIndex;

                                itemModel.Index--;

                                cOffset -= itemModel.UIElement.DesiredSize.Height;

                                MoveSN(itemModel.UIElement, barItem);

                            }
                        }

                    }
                    else if (isUp && cIndex > 0)
                    {
                        UIElement elt = (from model in ItemsModel where model.Index == cIndex - 1 select model.UIElement).FirstOrDefault();

                        if (elt.IsMeasureValid)
                        {
                            SwapPanelItem itemModel = elt.GetValue(SwapPanel.SwapPanelItemModelProperty) as SwapPanelItem;

                            if (itemModel != null && itemModel.YLocation > capturedItemModel.YLocation + distance + cOffset &&
                                itemModel.YLocation < capturedItemModel.YLocation)
                            {
                                capturedItemModel.Index = --cIndex;

                                itemModel.Index++;

                                cOffset += itemModel.UIElement.DesiredSize.Height;

                                MoveSN(itemModel.UIElement, barItem, false);
                            }
                        }
                    }
                }
            }

        }

       /// <summary>
       /// Occurs when dragging is started.
       /// </summary>
       /// <param name="sender">Owner of the event.</param>
       /// <param name="e">Event data for the event.</param>
        private void item_DragStarted(object sender, DragStartedEventArgs e)
        {
            sPoint = Mouse.GetPosition(this);

            SwapPanelItem item = (sender as DependencyObject).GetValue(SwapPanel.SwapPanelItemModelProperty) as SwapPanelItem;

            if (item == null) 
            {
                throw new NullReferenceException("Model cannot be null. Parameter name: model");
            }

            cIndex = item.Index;
            
            cOffset = 0;

            pValue = 0;
        }

        #endregion

        //------------------------------------------------------
        //
        //  Overrides
        //
        //------------------------------------------------------
        #region Overrides

        /// <summary>
        /// Invalidates the measurement state (layout) for the element. (Inherited from UIElement.)
        /// </summary>
        /// <param name="availableSize">The available size that this element can give to child elements. 
        /// Infinity can be specified as a value to indicate that the element will size to whatever content is available.</param>
        /// <returns>The size that this element determines it needs during layout, based on its calculations of child element sizes.</returns>
        protected override Size MeasureOverride(Size availableSize)
        {
            double x = 0, y = 0;

            UIElement elt = null;

            if (Orientation == System.Windows.Controls.Orientation.Horizontal)
            {
                if (!double.IsPositiveInfinity(availableSize.Height))
                {
                    y = availableSize.Height;
                }

                for (int i = 0; i < InternalChildren.Count; i++)
                {
                    elt = InternalChildren[i];

                    elt.Measure(infiniteSize);

                    x += elt.DesiredSize.Width;

                    y = Math.Max(y, elt.DesiredSize.Height);
                }

            }
            else
            {
                if (!double.IsPositiveInfinity(availableSize.Width))
                {
                    x = availableSize.Width;
                }
                for (int i = 0; i < InternalChildren.Count; i++)
                {
                    elt = InternalChildren[i];

                    elt.Measure(infiniteSize);

                    y += elt.DesiredSize.Height;

                    x = Math.Max(x, elt.DesiredSize.Width);
                }
            }

            return new Size(x, y);
        }

       /// <summary>
        /// Invalidates the arrange state (layout) for the element. 
        /// After the invalidation, the element will have its layout updated, which will occur 
        /// asynchronously unless subsequently forced by UpdateLayout. (Inherited from UIElement.)
       /// </summary>
        /// <param name="finalSize">The finalSize size that this element can give to child elements. 
        /// Infinity can be specified as a value to indicate that the element will size to whatever content is available.</param>
        /// <returns>The size that this element determines it needs during layout, based on its calculations of child element sizes.</returns>
        protected override Size ArrangeOverride(Size finalSize)
        {
            double x = 0, y = 0;

            UIElement elt = null;
            
            if (Orientation == System.Windows.Controls.Orientation.Horizontal)
            {
                for (int i = 0; i < InternalChildren.Count; i++)
                {
                    SwapPanelItem model = (from m in ItemsModel where m.Index == i select m).FirstOrDefault();

                    if (model == null)
                    {
                        elt = InternalChildren[i];

                        model = new SwapPanelItem()
                        {
                            UIElement = elt,
                            Index = i
                        };

                        elt.SetValue(SwapPanel.SwapPanelItemModelProperty, model);

                        ItemsModel.Add(model);
                    }
                    else 
                    {
                        elt = model.UIElement;
                    }

                    rect.Width = elt.DesiredSize.Width;

                    rect.Height = !double.IsPositiveInfinity(finalSize.Height) ? Math.Max(elt.DesiredSize.Height, finalSize.Height) : elt.DesiredSize.Height;

                    if (model.XLocation != x) 
                    {
                        model.XLocation = x;
                    }
                    if (model.YLocation != y) 
                    {
                        model.YLocation = y;
                    }
                    
                    rect.X = model.XLocation;
                   
                    rect.Y = model.YLocation;

                    if (elt.RenderTransform != null && elt.RenderTransform is TranslateTransform)
                    {
                        TranslateTransform translate = elt.RenderTransform as TranslateTransform;

                        if (translate != null)
                        {
                            ValueSource source = DependencyPropertyHelper.GetValueSource(translate, TranslateTransform.XProperty);
                            
                            translate.X = 0;
                            
                            if (source.IsAnimated)
                            {
                                translate.BeginAnimation(TranslateTransform.XProperty, null);
                            }
                        }
                    }

                    elt.Arrange(rect);
                 
                    x += elt.DesiredSize.Width;
                }
            }
            else
            {
                for (int i = 0; i < InternalChildren.Count; i++)
                {

                    SwapPanelItem model = (from m in ItemsModel where m.Index == i select m).FirstOrDefault();

                    if (model == null)
                    {
                        elt = InternalChildren[i];

                        model = new SwapPanelItem()
                        {
                            UIElement = elt,
                            Index = i
                        };

                        elt.SetValue(SwapPanel.SwapPanelItemModelProperty, model);

                        ItemsModel.Add(model);
                    }
                    else
                    {
                        elt = model.UIElement;
                    }

                    rect.Height = elt.DesiredSize.Height;

                    rect.Width = !double.IsPositiveInfinity(finalSize.Width) ? Math.Max(elt.DesiredSize.Width, finalSize.Width) : elt.DesiredSize.Width;

                    if (model.XLocation != x)
                    {
                        model.XLocation = x;
                    }
                    rect.X = model.XLocation;

                    if (model.YLocation != y)
                    {
                        model.YLocation = y;
                    }

                    rect.Y = model.YLocation;

                    if (elt.RenderTransform != null && elt.RenderTransform is TranslateTransform)
                    {
                        TranslateTransform translate = elt.RenderTransform as TranslateTransform;

                        if (translate != null)
                        {
                            ValueSource source = DependencyPropertyHelper.GetValueSource(translate, TranslateTransform.YProperty);

                            if (source.IsAnimated)
                            {
                                translate.Y = 0;
                                translate.BeginAnimation(TranslateTransform.YProperty, null);

                            }
                        }
                    }

                    elt.Arrange(rect);

                    y += elt.DesiredSize.Height;
                }
            }

            return finalSize;
        }

       /// <summary>
        /// Called when the VisualCollection of the visual object is modified. (Inherited from Visual.)
       /// </summary>
       /// <param name="visualAdded">Type: System.Windows.DependencyObject. The Visual that was added to the collection</param>
       /// <param name="visualRemoved">Type: System.Windows.DependencyObject. The Visual that was removed from the collection</param>
        protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
        {
            base.OnVisualChildrenChanged(visualAdded, visualRemoved);

            if (visualRemoved is UIElement) 
            {
                UIElement element = visualRemoved as UIElement;

                SwapPanelItem item = element.GetValue(SwapPanel.SwapPanelItemModelProperty) as SwapPanelItem;
                
                if (item != null) 
                {
                    ItemsModel.Remove(item);
                }
            }
        }

        #endregion

        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------
        #region Private Methods

       /// <summary>
       /// 
       /// </summary>
        private void BuildAnimation()
        {
            TranslateXStoryboard = new Storyboard();
            DXAnimation = new DoubleAnimation() { EasingFunction = new ExponentialEase() { EasingMode = EasingMode.EaseOut, Exponent = 10 } };
            DXAnimation.Duration = new Duration(TimeSpan.FromSeconds(0.15));
            PropertyPath path = new PropertyPath(Constants.XPropertyPath);
            Storyboard.SetTargetProperty(DXAnimation, path);
            TranslateXStoryboard.Children.Add(DXAnimation);
            
            TranslateYStoryboard = new Storyboard();
            DYAnimation = new DoubleAnimation() 
            { 
                EasingFunction = new ExponentialEase() 
                { 
                    EasingMode = EasingMode.EaseOut, 
                    Exponent = 10 
                } 
            };

            DYAnimation.Duration = new Duration(TimeSpan.FromSeconds(0.15));
            path = new PropertyPath(Constants.YPropertyPath);
            Storyboard.SetTargetProperty(DYAnimation, path);
            TranslateYStoryboard.Children.Add(DYAnimation);
        }

        /// <summary>
        /// Moves item to left-right or vice versa.
        /// </summary>
        /// <param name="element">Element which is moved.</param>
        /// <param name="isRight">Default value is true. If value is true then item is moved to right otherwise to left</param>
        private void MoveWE(UIElement element, UIElement capturedItem,  bool isRight = true)
        {
            TranslateTransform translate = element.RenderTransform as TranslateTransform;
            if (translate != null)
            {
                double offset = translate.X;

                if (!isRight)
                {
                    offset -= capturedItem.DesiredSize.Width;
                }
                else 
                {
                    offset += capturedItem.DesiredSize.Width;
                }

                DXAnimation.To = offset;

                Storyboard.SetTarget(DXAnimation, element);

                SwapPanelItem model = element.GetValue(SwapPanel.SwapPanelItemModelProperty) as SwapPanelItem;

                double a = model.XLocation, b = a + capturedItem.DesiredSize.Width;

                if (!isRight)
                {
                    b = a - capturedItem.DesiredSize.Width;
                }
                model.XLocation = b;

                model = capturedItem.GetValue(SwapPanel.SwapPanelItemModelProperty) as SwapPanelItem;

                if (isRight)
                {
                    model.XLocation = a;
                }
                else
                {
                    model.XLocation = b + element.DesiredSize.Width;
                }

                TranslateXStoryboard.Begin();
            }
        }

       /// <summary>
       /// 
       /// </summary>
        private void MoveSN(UIElement element, UIElement capturedItem, bool isUp = true)
        {
            TranslateTransform translate = element.RenderTransform as TranslateTransform;

            if (translate!=null)
            {
                double offset = translate.Y;


                if (!isUp)
                {
                    offset += capturedItem.DesiredSize.Height;
                }
                else 
                {
                    offset -= capturedItem.DesiredSize.Height;
                }

                DYAnimation.To = offset;

                Storyboard.SetTarget(DYAnimation, element);

                SwapPanelItem model = element.GetValue(SwapPanel.SwapPanelItemModelProperty) as SwapPanelItem;

                if (model == null)
                {
                    throw new InvalidOperationException("SwapPanelItemModel cannot be null");
                }
                double a = model.YLocation, b = a - capturedItem.DesiredSize.Height;
                if (!isUp)
                {
                    b = a + capturedItem.DesiredSize.Height;
                }
                model.YLocation = b;

                model = capturedItem.GetValue(SwapPanel.SwapPanelItemModelProperty) as SwapPanelItem;

                if (model == null)
                {
                    throw new InvalidOperationException("SwapPanelItemModel cannot be null");
                }
                if (isUp)
                {
                    model.YLocation = b + element.DesiredSize.Height;
                }
                else
                {
                    model.YLocation = a;
                }

                TranslateYStoryboard.Begin();
            }
        }

        #endregion

        //------------------------------------------------------
        //
        //  Event Handlers
        //
        //------------------------------------------------------
        #region Event Handlers
        #endregion

        //------------------------------------------------------
        //
        //  Fields
        //
        //------------------------------------------------------
        #region Fields

        private Storyboard TranslateXStoryboard, TranslateYStoryboard/*, SizeStoryboard*/;
        private DoubleAnimation DXAnimation, DYAnimation;
        private Size infiniteSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
        private Rect rect = new Rect();
        internal List<SwapPanelItem> ItemsModel = new List<SwapPanelItem>();
        private TranslateTransform translate = new TranslateTransform();
        private Point sPoint = new Point(), emptyPoint = new Point(0, 0);
        int cIndex = 0;
        double cOffset = 0, pValue = 0;

        #endregion

        //------------------------------------------------------
        //
        //  Events
        //
        //------------------------------------------------------
        #region Events
        #endregion

    }
}
